﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
//using System.Windows.Media.Imaging;
using LowLevelGraphics.Histograms;

namespace LowLevelGraphics.Filter
{
    public enum COLORPALETTEMATCHING
    {
        DEFAULT,
        APPLY_MATRIX,
        APPLY_MAP
    }

    //TODO: check if this does make sense with other color spaces
    /// <summary>
    /// This is used to reduce colors of a pic.
    /// All Colors will be matched to the given palette.
    /// 
    /// This is used to reduce the amount of colors in a pic.
    /// e.g. you want to assign another palette.
    /// </summary>
    public class ColorPaletteMatching : BaseImageFilter
    {
        protected COLORPALETTEMATCHING M_COLORPALETTEMATCHING = COLORPALETTEMATCHING.DEFAULT;
        protected ColorMap[] m_aColorMap = null;
        protected ColorMatrix m_aColorMatrix = null;

        protected IEnumerable<Color> m_aPalette = new List<Color>() {
            
            Color.DarkOrange,
            Color.Orange,
            Color.OrangeRed,

            Color.DarkRed,
            Color.Red, 
            
            Color.DarkOliveGreen,
            Color.DarkGreen,
            Color.Green, 
            
            Color.DarkBlue,
            Color.Blue, 
            Color.LightBlue,
            
            Color.Brown,
            Color.Yellow, 
            Color.LightYellow,
            
            Color.DarkCyan,
            Color.Cyan, 
            
            Color.DarkMagenta,
            Color.Magenta, 
            
            Color.DarkGray,
            Color.Gray, 
            Color.LightGray,
            
            Color.Black, 
            Color.White
        };

        protected Dictionary<Color, Color> aLookUp = new Dictionary<Color, Color>();

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorPaletteMatching"/> class.
        /// </summary>
        /// <param name="_colorMatrix">The _color matrix.</param>
        public ColorPaletteMatching(ColorMatrix _colorMatrix)
        {
            m_aColorMatrix = _colorMatrix;
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Drawing.Imaging.ColorMatrix"/> to <see cref="LowLevelGraphics.Filter.ColorPaletteMatching"/>.
        /// </summary>
        /// <param name="_colorMatrix">The _color matrix.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator ColorPaletteMatching(ColorMatrix _colorMatrix)
        {
            return new ColorPaletteMatching(_colorMatrix);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorPaletteMatching"/> class.
        /// </summary>
        /// <param name="_colorMap">The _color map.</param>
        /// <param name="_bApplyColorMap">if set to <c>true</c> [_b apply color map].</param>
        public ColorPaletteMatching(ColorMap[] _colorMap, bool _bApplyColorMap)
        {
            m_aColorMap = _colorMap;
            if (_bApplyColorMap)
            {
                M_COLORPALETTEMATCHING = COLORPALETTEMATCHING.APPLY_MAP;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorPaletteMatching"/> class.
        /// </summary>
        /// <param name="_colorMap">The _color map.</param>
        public ColorPaletteMatching(ColorMap[] _colorMap)
        {
            //This is done to precache a little
            foreach (ColorMap colorMap in _colorMap)
            {
                aLookUp.Add(colorMap.OldColor, colorMap.NewColor);
            }
        }

        /// <summary>
        /// Performs an implicit conversion from <see cref="System.Drawing.Imaging.ColorPalette"/> to <see cref="LowLevelGraphics.Filter.ColorPaletteMatching"/>.
        /// </summary>
        /// <param name="_colorPalette">The _color palette.</param>
        /// <returns>The result of the conversion.</returns>
        public static implicit operator ColorPaletteMatching(ColorPalette _colorPalette)
        {
            return new ColorPaletteMatching(_colorPalette);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorPaletteMatching"/> class.
        /// </summary>
        /// <param name="_colorPalette">The _color palette.</param>
        public ColorPaletteMatching(ColorPalette _colorPalette)
        {
            m_aPalette = _colorPalette.Entries;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorPaletteMatching"/> class.
        /// </summary>
        /// <param name="_bUseBasicColorList">if set to <c>true</c> [_b use basic color list].</param>
        public ColorPaletteMatching(bool _bUseBasicColorList)
        {
            if (!_bUseBasicColorList)
            {
                m_aPalette = Definitions.KnownColors;
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ColorPaletteMatching"/> class.
        /// </summary>
        public ColorPaletteMatching()
        {
            m_aPalette = Definitions.KnownColors;
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="ColorPaletteMatching"/> class.
        /// </summary>
        /// <param name="_aPalette">The _a palette.</param>
        public ColorPaletteMatching(IEnumerable<Color> _aPalette)
        {
            m_aPalette = _aPalette;
        }

        /// <summary>
        /// Gets or sets the color palette.
        /// </summary>
        /// <value>The color palette.</value>
        public IEnumerable<Color> ColorPalette
        {
            get { return m_aPalette; }
            set { m_aPalette = value; }
        }

        /// <summary>
        /// Executes this filter
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            UnsafeBitmap bitmap = _bitmap;

            switch (M_COLORPALETTEMATCHING)
            {
                case COLORPALETTEMATCHING.DEFAULT:
                    ApplyColorMapByIEnumerableColor(bitmap);
                    break;

                case COLORPALETTEMATCHING.APPLY_MAP:
                    ApplyColorMap(_bitmap, m_aColorMap);
                    break;

                case COLORPALETTEMATCHING.APPLY_MATRIX:
                    ApplyMatrix(_bitmap, m_aColorMatrix);
                    break;
            }

            return _bitmap;
        }

        /// <summary>
        /// Gets or sets the color palette matching mode.
        /// </summary>
        /// <value>The color palette matching mode.</value>
        public COLORPALETTEMATCHING ColorPaletteMatchingMode
        {
            get { return M_COLORPALETTEMATCHING; }
            set { M_COLORPALETTEMATCHING = value; }
        }

        /// <summary>
        /// Applies the color map.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_aColorMap">The _a color map.</param>
        private void ApplyColorMap(UnsafeBitmap _bitmap, ColorMap[] _aColorMap)
        {
            Image grayScale = new Bitmap(_bitmap.Width, _bitmap.Height);
            Image colored = _bitmap.InternalBitmap;

            // Zeichenflaeche erzeugen :
            Graphics canvas = Graphics.FromImage ( colored );

            // Bild - Attribute erstellen um die Farb - Mapping durchfuehren
            // zu koennen :
            ImageAttributes mapper = new ImageAttributes ();
            mapper.SetRemapTable(_aColorMap);

            // Das Graustufenbild auf der Zeichenflaeche zeichnen unter Verwendung
            // des Farb - Mappings :
            canvas.DrawImage(grayScale, new Rectangle (0, 0,colored .Width ,colored . Height ));
        }

        /// <summary>
        /// Applies the matrix.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_colorMatrix">The _color matrix.</param>
        private void ApplyMatrix(UnsafeBitmap _bitmap, ColorMatrix _colorMatrix)
        {
            _bitmap.Dispose();
            Image img = (Image)_bitmap.InternalBitmap;
            ImageAttributes imageAttrs = new ImageAttributes();
            imageAttrs.SetColorMatrix(_colorMatrix);
            using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(img))
            {
                g.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height),
                    0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imageAttrs);
            }
        }

        /// <summary>
        /// Applies the color of the color map by I enumerable.
        /// </summary>
        /// <param name="bitmap">The bitmap.</param>
        private void ApplyColorMapByIEnumerableColor(UnsafeBitmap bitmap)
        {

            aLookUp.Clear();
            int x = 0;
            int y = 0;

            int nWidth = bitmap.Width;
            int nHeight = bitmap.Height;

            Color color = Color.Transparent;
            int nMaxDistance = ColorFunctions.DistanceSquared(Definitions.White, Definitions.Black);
            int nTempDistance = 0;
            int nDistance = nMaxDistance;

            Color colorNew = Color.Transparent;

            Color colorTemp = Color.Transparent;

            //First we get the palette of loaded pic

            //TODO: this should be used, when palette reading is done match faster


            HashSet<Color> aPalette = bitmap.Palette;

            //Then we create a lookup table

            bool bPrecache = false;

            if (bPrecache)
            {
                CalculatePrecacheTable(
                    aLookUp,
                    nMaxDistance,
                    ref nTempDistance,
                    ref nDistance,
                    ref colorNew,
                    ref colorTemp,
                    aPalette);
            }

            //Now all left we've got to do is to use the lookup table.
            //If we find no color matching in the lookup table we calculate the best fitting color and add it

            for (y = 0; y < nHeight; y++)
            {
                for (x = 0; x < nWidth; x++)
                {
                    color = bitmap.GetPixel(x, y);
                    if (!bPrecache)
                    {
                        if (aLookUp.ContainsKey(color))
                        {
                            colorNew = aLookUp[color];
                        }
                        else
                        {
                            BestFittingColor(ref color, nMaxDistance, ref nTempDistance, ref nDistance, ref colorNew);
                            aLookUp.Add(color, colorNew);
                        }
                    }
                    else
                    {
                        colorNew = aLookUp[color];
                    }
                    bitmap.SetPixel(x, y, colorNew);
                }
            }
        }

        /// <summary>
        /// Calculates the precache table.
        /// </summary>
        /// <param name="aLookUp">A look up.</param>
        /// <param name="nMaxDistance">The n max distance.</param>
        /// <param name="nTempDistance">The n temp distance.</param>
        /// <param name="nDistance">The n distance.</param>
        /// <param name="colorNew">The color new.</param>
        /// <param name="colorTemp">The color temp.</param>
        /// <param name="aPalette">A palette.</param>
        private void CalculatePrecacheTable(Dictionary<Color, Color> aLookUp, int nMaxDistance, ref int nTempDistance, ref int nDistance, ref Color colorNew, ref Color colorTemp, HashSet<Color> aPalette)
        {
            foreach (Color colorPalette in aPalette)
            {
                colorTemp = colorPalette;

                BestFittingColor(ref colorTemp, nMaxDistance, ref nTempDistance, ref nDistance, ref colorNew);
                aLookUp.Add(colorTemp, colorNew);
            }
        }

        /// <summary>
        /// Bests the color of the fitting.
        /// </summary>
        /// <param name="color">The color.</param>
        /// <param name="nMaxDistance">The n max distance.</param>
        /// <param name="nTempDistance">The n temp distance.</param>
        /// <param name="nDistance">The n distance.</param>
        /// <param name="colorNew">The color new.</param>
        private void BestFittingColor(ref Color color, int nMaxDistance, ref int nTempDistance, ref int nDistance, ref Color colorNew)
        {
            nDistance = nMaxDistance;

            if (m_aPalette.Contains(color))
            {
                return;
            }

            foreach(Color colorTemp in m_aPalette)
            {
                nTempDistance = ColorFunctions.DistanceSquared(color, colorTemp);
                if (nTempDistance < nDistance)
                {
                    nDistance = nTempDistance;
                    colorNew = colorTemp;
                    if (nDistance == 0)
                    {
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// This is used to clone a filter, each filter must implement it's own clone functionality
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new ColorPaletteMatching(this.m_aPalette);
        }
    }
}
